/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtest/gtest.h"
#include "test_helper.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/js_object.h"
#include "plugins/ecmascript/runtime/js_hclass.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/js_function_extra_info.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "include/runtime.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

namespace ark::test {
class JSFunctionTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {};

private:
    PandaVM *instance_ {nullptr};
    ecmascript::EcmaHandleScope *scope_ {nullptr};
};

JSFunction *JSObjectCreate(JSThread *thread)
{
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> globalEnv = ecmaVm->GetGlobalEnv();
    return globalEnv->GetObjectFunction().GetObject<JSFunction>();
}

TEST_F(JSFunctionTest, Create)
{
    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSFunction> funHandle = thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env);
    EXPECT_TRUE(*funHandle != nullptr);
    EXPECT_EQ(funHandle->GetProtoOrDynClass(), JSTaggedValue::Hole());

    JSHandle<LexicalEnv> lexicalEnv = thread_->GetEcmaVM()->GetFactory()->NewLexicalEnv(0);
    funHandle->SetLexicalEnv(thread_, lexicalEnv.GetTaggedValue());
    EXPECT_EQ(funHandle->GetLexicalEnv(), lexicalEnv.GetTaggedValue());
    EXPECT_TRUE(*lexicalEnv != nullptr);
}
TEST_F(JSFunctionTest, MakeConstructor)
{
    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSFunction> func = thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, static_cast<void *>(nullptr),
                                                                                  FunctionKind::BASE_CONSTRUCTOR);
    EXPECT_TRUE(*func != nullptr);
    JSHandle<JSTaggedValue> funcHandle(func);
    func->GetJSHClass()->SetExtensible(true);

    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> obj = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSTaggedValue> objValue(obj);

    JSFunction::MakeConstructor(thread_, func, objValue);

    JSHandle<JSTaggedValue> constructorKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("constructor"));

    JSHandle<JSTaggedValue> protoKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("prototype"));
    JSTaggedValue proto = JSObject::GetProperty(thread_, funcHandle, protoKey).GetValue().GetTaggedValue();
    JSTaggedValue constructor =
        JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), constructorKey).GetValue().GetTaggedValue();
    EXPECT_EQ(constructor, funcHandle.GetTaggedValue());
    EXPECT_EQ(proto, obj.GetTaggedValue());
    EXPECT_EQ(func->GetFunctionKind(), FunctionKind::BASE_CONSTRUCTOR);
}

TEST_F(JSFunctionTest, OrdinaryHasInstance)
{
    JSHandle<JSTaggedValue> objFun(thread_, JSObjectCreate(thread_));

    JSHandle<JSObject> jsobject =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
    JSHandle<JSTaggedValue> obj(thread_, jsobject.GetTaggedValue());
    EXPECT_TRUE(*jsobject != nullptr);

    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> globalEnv = ecmaVm->GetGlobalEnv();
    JSHandle<JSTaggedValue> constructor = globalEnv->GetObjectFunction();
    EXPECT_TRUE(ecmascript::JSFunction::OrdinaryHasInstance(thread_, constructor, obj));
}

JSTaggedValue TestInvokeInternal(EcmaRuntimeCallInfo *argv)
{
    if (argv->GetArgsNumber() == 1 && argv->GetCallArg(0).GetTaggedValue() == JSTaggedValue(1)) {
        return builtins_common::GetTaggedBoolean(true);
    }
    return builtins_common::GetTaggedBoolean(false);
}

TEST_F(JSFunctionTest, Invoke)
{
    EcmaVM *ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectCreate(thread_));
    JSHandle<JSTaggedValue> callee(
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>::Cast(dynclass), dynclass));
    EXPECT_TRUE(*callee != nullptr);

    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    char keyArray[] = "invoked";
    JSHandle<JSTaggedValue> calleeKey(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(&keyArray[0]));
    JSHandle<JSFunction> calleeFunc =
        thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, reinterpret_cast<void *>(TestInvokeInternal));
    JSHandle<JSTaggedValue> calleeValue(calleeFunc);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(callee), calleeKey, calleeValue);

    auto info = NewRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), callee, JSTaggedValue::Undefined(), 1);
    info->SetCallArgs(JSTaggedValue(1));
    JSTaggedValue res = JSFunction::Invoke(info.Get(), calleeKey);

    JSTaggedValue ruler = builtins_common::GetTaggedBoolean(true);
    EXPECT_EQ(res.GetRawData(), ruler.GetRawData());
}

TEST_F(JSFunctionTest, SetSymbolFunctionName)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> jsFunction = factory->NewJSFunction(env);
    JSHandle<JSSymbol> symbol = factory->NewPublicSymbolWithChar("name");
    JSHandle<EcmaString> name = factory->NewFromString("[name]");
    JSHandle<JSTaggedValue> prefix(thread_, JSTaggedValue::Undefined());
    JSFunction::SetFunctionName(thread_, JSHandle<JSFunctionBase>(jsFunction), JSHandle<JSTaggedValue>(symbol), prefix);
    JSHandle<JSTaggedValue> functionName =
        JSFunctionBase::GetFunctionName(thread_, JSHandle<JSFunctionBase>(jsFunction));
    EXPECT_TRUE(functionName->IsString());
    EXPECT_TRUE(EcmaString::StringsAreEqual(*(JSHandle<EcmaString>(functionName)), *name));
}

}  // namespace ark::test
